package cnhy.salary.service;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cnhy.salary.domain.*;
import cnhy.salary.domain.dto.AttendanceMaxDto;
import cnhy.salary.domain.dto.EmployeeDto;
import cnhy.salary.domain.dto.MonthlyDto;
import cnhy.salary.domain.dto.WageDictCfg;
import com.easy.query.core.proxy.sql.GroupKeys;
import com.easy.query.core.proxy.sql.Select;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import ling.common.DataUtil;
import ling.common.EQueryService;
import ling.common.domain.Result;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.collections4.keyvalue.DefaultKeyValue;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@Slf4j
public class WageSvc extends EQueryService<MonthlyWage> {

    /** 手动创建一个副岗日工资草稿
     *
     * @param dw {userId:用户id,time:日期,postId:岗位id,deptId:部门id}
     * @return
     */

    //创建
    public Result<DailyWage> createDailyWage(DailyWage dw){
        long userId=dw.getUserId();
        Date time=dw.getTime();
        long postCfgId =dw.getPostId();
        long deptId=dw.getDeptId();
        var oldDeptDW=queryHelper.entityQuery(DailyWage.class)
                .where(p->p.deptId().eq(deptId))
                .where(d -> d.status().eq("1"))
                .where(p->p.time().rangeClosed(DateUtil.beginOfDay(time),DateUtil.endOfDay(time)))
                .firstOrNull();
        if(oldDeptDW!=null)
            return Result.bad("班组工资已提交，不能再新建工资项");
        var emplDto=queryHelper.queryable(EmployeeDto.class).where(e -> e.eq(EmployeeDto::getUserId,userId)).firstOrNull();
        var postCfg=queryHelper.entityQuery(PostCfg.class).whereById(postCfgId).firstOrNull();
        var dept=queryHelper.queryable(Department.class).where(d ->d.eq(Department::getDeptId, deptId)).firstOrNull();
        if(emplDto==null||postCfg==null||dept==null){
            return Result.bad("用户、部门或岗位不存在");
        }
        resetMonthlyCfgCache();
        var entity=toDailyWage(emplDto,time,postCfg,dept);
        queryHelper.query().insertable(entity).executeRows(true);
        return Result.ok(entity);
    }

    private DailyWage toDailyWage(EmployeeDto item, Date time,PostCfg tempPost,Department tempDept) {
        var lineId=item.getLineId();
        if(tempDept!=null)
            lineId=tempDept.getLineId();
        var mcfg = getMonthlyCfg(time,lineId);
        if (mcfg == null)
            throw new IllegalArgumentException("无法找到月产线配置信息！产线id:"+lineId);
        //主岗
        if(tempPost==null){
            var dailyWage = getDailyWage(item, null,null);
            dailyWage.setTime(time);
            dailyWage.ratioBaseSet(item.getRank(), mcfg);
            return dailyWage;
        }
        //副岗
        var tempDW = getDailyWage(item, tempPost,tempDept);
        tempDW.setTime(time);
        tempDW.ratioBaseSet(tempPost.getRank(), mcfg);
        return tempDW;
    }
    @Transactional
    public void createDailyBill(Date timeSet) {
        //每日凌晨生成昨天的日工资
        //日工资计算：0点对每个人生成产出草稿：记录岗位配置信息，班长提交时：使用记录的配置信息计算各项
        //对于临时调岗：如果有副岗，就生成两条，班长自己填
        try {

//            var list = queryHelper.queryable(EmployeeDto.class)
//                    .where(e -> e.isNull(EmployeeDto::getResignTime))
//                    .where(e -> e.isNotNull(EmployeeDto::getPost)).toList();
            var time = timeSet == null ? DateUtils.addDays(new Date(), -1) : timeSet;
            var params=new ArrayList<Object>();
            params.add(DateUtil.format(time,"yyyyMMdd"));
          var list= queryHelper.query().queryable(dailyWageSql,EmployeeDto.class,params)
                    .where(e -> e.isNull(EmployeeDto::getResignTime))
                    .where(e -> e.isNotNull(EmployeeDto::getPost)).toList();
            var dwList = new ArrayList<DailyWage>();
            var postList = queryHelper.entityQuery(PostCfg.class).toList();
            var deptList=queryHelper.queryable(Department.class).where(p->p.isNotNull(Department::getLineId)).toList();

            resetMonthlyCfgCache();
            for (var item : list) {
                toDailyWageByEmplInfo(item, time, dwList, postList,deptList);
            }
            queryHelper.entityQuery().insertable(dwList).executeRows();
            //清除过期员工副岗
            clearTempPost(DateUtil.endOfDay(time));
        } catch (Exception ex) {
            log.error("创建日工资异常",ex);
            throw ex;
        }
    }
  String dailyWageSql="SELECT e.*,l.user_id leave_user_id from v_employee_info e left join (select user_id from pay_leave_record WHERE  duration=1  and DATE_FORMAT(begin_time,'%Y%m%d')=? GROUP BY user_id) l on l.user_id=e.user_id where l.user_id is null";
    List<Long> DailyLeave1Get(Date time) {
        var list = queryHelper.entityQuery(LeaveRecord.class)
                .where(p -> p.status().eq(100))
                .where(p -> p.beginTime().rangeClosed(DateUtil.beginOfDay(time), DateUtil.endOfDay(time)))
                .where(p -> p.duration().eq(1d))
                .groupBy(p -> GroupKeys.of(p.userId()))
                .select(p -> p.key1())
                .toList();
        return list;
    }
    public boolean leaveToWage(LeaveRecord leaveRecord) {
        if (leaveRecord.getStatus() != 100 || leaveRecord.getDuration() != 1)
            return false;
        var wage = queryHelper.entityQuery(DailyWage.class)
                .where(p -> p.userId().eq(leaveRecord.getUserId()))
                .where(p -> p.time().rangeClosed(DateUtil.beginOfDay(leaveRecord.getBeginTime()), DateUtil.endOfDay(leaveRecord.getBeginTime())))
                .where(p -> p.postStatus().eq("0"))
                .firstOrNull();
        if (wage == null|| !Objects.equals(wage.getStatus(), "0"))
            return false;
        wage.setWorkingStatus("2");
        wage.setRemark("请假;"+DataUtil.nullDefault(wage.getRemark(),""));
        queryHelper.entityQuery().updatable(wage).executeRows();
        return true;
    }
    private void toDailyWageByEmplInfo(EmployeeDto item, Date time, ArrayList<DailyWage> dwList, List<PostCfg> postList,List<Department> deptList) {
        var mcfg = getMonthlyCfg(time, item.getLineId());
        if(mcfg==null)
            throw new  IllegalArgumentException("无法找到月产线配置信息！"+item.getLineId());
        //生成日工资记录
        var dailyWage = getDailyWage(item, null,null);
        dailyWage.setTime(time);
        dailyWage.ratioBaseSet(item.getRank()==null?0: item.getRank(), mcfg);
        dailyWage.setFixedWage(item.getFixedWage());
        dailyWage.setFixedPost(item.getFixedWage());
        dailyWage.setPieceworkPost(item.getPieceworkWage());
        dailyWage.setPostStatus("0");

        //
        item.nullDoubleToDefault();
        // 日/月考勤比例,日工龄，日岗位津贴
        var dayMR= 1/mcfg.getWorkingDays();
        var fillAgeWage=getAgeWage(item.getEntryTime(), time);
        //保留两位
        dailyWage.setWorkingAge(NumberUtil.round(fillAgeWage*dayMR,2).doubleValue());
        dailyWage.setPostAllowance(NumberUtil.round( item.getPostAllowance()*dayMR,2).doubleValue());

        //保存下
        dwList.add(dailyWage);
        //无副岗或未生效
        if (item.getTempPost() == null || !org.apache.commons.lang3.time.DateUtils.isSameDay(dailyWage.getTime(), item.getTempBegin()))
            return;
        //有副岗

        //查找副岗
        var rePost = postList.stream().filter(p -> Objects.equals(p.getId(), item.getTempPost())).findAny();
        if (rePost.isPresent()) {
            var deptRe=deptList.stream().filter(p-> Objects.equals(p.getDeptId(), item.getTempDept())).findAny();
            mcfg = deptRe.map(department -> getMonthlyCfg(time, department.getLineId())).orElse(null);
            var tempDW = new DailyWage();
            tempDW.setPostStatus("1");
            var post = rePost.get();
            tempDW = getDailyWage(item, post,deptRe.get());
            tempDW.setTime(time);
            tempDW.ratioBaseSet(post.getRank(), mcfg);  //副岗未找到月配置则基数设为0
            tempDW.setFixedWage(rePost.get().getFixedWage());
            tempDW.setFixedPost(rePost.get().getFixedWage());
            tempDW.setPieceworkPost(rePost.get().getPieceworkWage());
            dwList.add(tempDW);   //保存副岗日工资
        } else {
            //错误的临时岗位id
        }
    }

    public Result<Integer> submitDailyBill(List<DailyWage> dailyWages) {
        try {
            // 检查工资日期：只能提交前一天及以前的工资
            var today = DateUtil.beginOfDay(new Date());
            
            for (var wage : dailyWages) {
                if (wage.getTime() != null && !wage.getTime().before(today)) {
                    return Result.bad("产出第二天才能提交！");
                }
            }

            for (var item : dailyWages) {
                var p = item;
                if (p.getOutputNum() == null && p.getFixedPost() == null && p.getPieceworkNum() == null)
                    return Result.bad("工作量不能都为空！");
            }
            var ids = dailyWages.stream().map(p -> p.getId()).collect(Collectors.toList());
            var oldList = queryHelper.entityQuery(DailyWage.class)
                    .where(d -> d.status().eq("0"))
                    .whereByIds(ids).toList();
            if (oldList.size() != dailyWages.size())
                return Result.bad("数据行错误！");

            for (var old : oldList) {
                dailyWages.stream().filter(p -> Objects.equals(p.getId(), old.getId())).findAny().ifPresent(p -> {

                    old.setOutputNum(p.getOutputNum());
                    old.setFixedWage(p.getFixedPost());
                    old.setFixedPost(p.getFixedPost());
                    old.setPieceworkNum(p.getPieceworkNum());
                    old.setPerformance(p.getPerformance());
                    old.setScoreAdd(p.getScoreAdd());
                    old.typeWageSet();
                    old.setStatus("1");
                    old.setRemark(p.getRemark());
                    old.setUpdatedId(SecurityUtils.getUserId());
                    old.setUpdatedTime(new Date());
                    old.wageSetBy();
                    if (Objects.equals(old.getPostStatus(), "1"))
                        return;

                    if (p.getOtherWage()!=null){
                        old.setOtherWage(p.getOtherWage());
                        old.setMeal(0d);// 取消餐补、工龄工资、岗位津贴
                        old.setWorkingAge(0d);
                        old.setPostAllowance(0d);
                        old.setWage(p.getOtherWage());
                        old.setPieceworkWage(0d);
                        old.setFixedWage(0d);
                        old.setFixedPost(0d);
                        old.setRatioWage(0d);
                        old.setOutputNum(0d);
                        old.setRatio(0d);
                        return;
                    }


                    workingStatusSet(old, p); //休假
                    overtimeSet(old); //加班
                    if (DataUtil.nullDefault(old.getOvertimePay(), 0d) > 0 || Objects.equals(old.getWorkingStatus(), "0")) {
                        //默认已设置餐补
                    } else {
                        old.setMeal(0d);// 取消餐补、工龄工资、岗位津贴
                        old.setWorkingAge(0d);
                        old.setPostAllowance(0d);
                    }
                    old.wageSetBy();
                });
            }

            var re = queryHelper.entityQuery().updatable(oldList).executeRows();

            return Result.ok((int) re);
        } catch (Exception ex) {
            log.error("提交日工资异常",ex);
            throw ex;
        }
    }

    /** 编辑产出相关信息，pc端领导产出编辑页面
     *
     * @param dailyWages
     * @return
     */
    public Result<Integer> editLeaderDailyBill(List<DailyWage> dailyWages) {
        for (var item:dailyWages) {
            var p = item;
            if (p.getOutputNum() == null && p.getFixedPost() == null && p.getPieceworkNum() == null)
                return Result.bad("工作量不能都为空！");
        }

        var ids = dailyWages.stream().map(p -> p.getId()).collect(Collectors.toList());
        var oldList = queryHelper.entityQuery(DailyWage.class)
                .where(d -> d.status().eq("0"))
                .where(p->p.postRank().in(new Integer[]{1,2}))
                .whereByIds(ids).toList();
        if (oldList.size() != dailyWages.size())
            return Result.bad("数据行错误！");
        for (var old : oldList) {
            dailyWages.stream().filter(p -> Objects.equals(p.getId(), old.getId())).findAny().ifPresent(p -> {
                old.setOutputNum(p.getOutputNum());
                old.setFixedWage(p.getFixedWage());
                old.setFixedPost(p.getFixedWage());
                old.setPieceworkNum(p.getPieceworkNum());
                //
                old.setPerformance(p.getPerformance());
                old.setWorkingStatus(p.getWorkingStatus());

                old.typeWageSet();

                old.setUpdatedId(SecurityUtils.getUserId());
                old.setUpdatedTime(new Date());
            });
        }

        var re = queryHelper.entityQuery().updatable(oldList).executeRows();

        return Result.ok((int) re);
    }

    /** 更新日工资实际基数，通过月配置，按月更新
     *
     * @param cfg
     */
    @Transactional
    public void  updateDailyBaseFinal(MonthlyCfg cfg){

        var month=cfg.getMonth();
        var lv0Base=cfg.getLv1Final();
        var lv1Base=cfg.getLv2Final();
        var lv2Base=cfg.getLv3Final();
        queryHelper.entityQuery().updatable(DailyWage.class)
                .where(p->p.time().rangeOpen(DateUtil.beginOfMonth(month),DateUtil.endOfMonth(month)))
                .where(d -> d.postType().eq(2))
                .where(p->p.postRank().eq(0))
                .setColumns(p->p.ratioBaseFinal().set(lv0Base)).executeRows();
        queryHelper.entityQuery().updatable(DailyWage.class)
                .where(p->p.time().rangeOpen(DateUtil.beginOfMonth(month),DateUtil.endOfMonth(month)))
                .where(d -> d.postType().eq(2))
                .where(p->p.postRank().eq(1))
                .setColumns(p->p.ratioBaseFinal().set(lv1Base)).executeRows();
        queryHelper.entityQuery().updatable(DailyWage.class)
                .where(p->p.time().rangeOpen(DateUtil.beginOfMonth(month),DateUtil.endOfMonth(month)))
                .where(d -> d.postType().eq(2))
                .where(p->p.postRank().eq(2))
                .setColumns(p->p.ratioBaseFinal().set(lv2Base)).executeRows();


    }
    void overtimeSet(DailyWage dailyWage) {
        //餐补-已有，
        // 加班，查询加班申请
//        var overtime =new WageDictCfg().overtime;
        //查询所有加班记录
        var recordList = queryHelper.entityQuery(OvertimeRecord.class)
                .where(o -> o.beginTime().rangeClosed(DateUtil.beginOfDay(dailyWage.getTime()), DateUtil.endOfDay(dailyWage.getTime())))
                .toList();
        recordList.stream().filter(p -> Objects.equals(p.getUserId(), dailyWage.getUserId())).findAny().ifPresent(p -> {
            dailyWage.setOvertimePay(p.getOvertimePay());
        });

    }

    void clearTempPost(Date time) {
        queryHelper.entityQuery().updatable(Employee.class)
                .where(d -> d.tempPost().isNotNull())
                .where(e -> e.tempBegin().lt(time))
                .setColumns(e -> {
                    e.tempPost().set((Long) null);
                    e.tempPostName().set((String) null);
                }).executeRows();
    }

    /** 日休假情况设置，休假则设置产出都为0
     *
     * @param dailyWage
     * @param input
     */
    void workingStatusSet(DailyWage dailyWage,DailyWage input) {
        //判断休假状态,影响是否上班，是否算正常考勤
        dailyWage.setWorkingStatus("0");
        if (Objects.equals(input.getWorkingStatus(), "1")) {
            dailyWage.setWorkingStatus("1");
            return;
        }
        //这里认为没有工作量就是休假？可能在副岗
//        var wage =DataUtil.nullDefault(dailyWage.getRatioWage(),0d) + DataUtil.nullDefault(dailyWage.getFixedWage(),0d) + DataUtil.nullDefault(dailyWage.getPieceworkWage(),0d);
//        if (wage < 1)
//            dailyWage.setWorkingStatus("1");
    }

    private static DailyWage getDailyWage(EmployeeDto item, PostCfg post,Department dept) {
        var dailyWage = new DailyWage();
        dailyWage.setUserId(item.getUserId());
        dailyWage.setUserName(item.getName());
        dailyWage.setStatus("0");
        dailyWage.setWorkingStatus("0");
        dailyWage.setLineName(item.getLineName());
        dailyWage.setLv2Id(item.getLv2Id());
        dailyWage.setLv2Name(item.getLv2Name());
        dailyWage.setOrderNum(item.getOrderNum());

        dailyWage.setCreatedTime(new Date());

        if (post == null) {
            dailyWage.setDeptId(item.getDeptId());
            dailyWage.setDeptName(item.getDeptName());
            //
            dailyWage.setPostId(item.getPost());
            dailyWage.setPostName(item.getPostName());
            dailyWage.setPostRank(item.getRank());
            dailyWage.setPostType(item.getPostType());
            dailyWage.setRatio(item.getRatioWage());
            dailyWage.setFixedPost(item.getFixedWage());//来自岗位
            dailyWage.setPieceworkPost(item.getPieceworkWage());//来自岗位
            //
            dailyWage.setMeal(item.getMealAllowance());

            return dailyWage;
        }
        //副岗
        dailyWage.setPostStatus("1");
        dailyWage.setDeptId(dept.getDeptId());
        dailyWage.setDeptName(dept.getDeptName());
        dailyWage.setLineName(dept.getLineName());
        //
        dailyWage.setLv2Id(post.getLv2Id());
        dailyWage.setLv2Name(post.getLv2Name());
        dailyWage.setPostId(post.getId());
        dailyWage.setPostName(post.getName());
        dailyWage.setPostRank(post.getRank());
        dailyWage.setPostType(post.getType());
        dailyWage.setRatio(post.getRatioWage());
        dailyWage.setFixedPost(post.getFixedWage());//来自岗位
        dailyWage.setPieceworkPost(post.getPieceworkWage());//来自岗位
        return dailyWage;
    }

    /** 重新生成月工资
     *
     * @param runMonth
     */
    @Transactional
    public Result resetMonthlyBill(Date runMonth) {
        // runMonth 是月初时间，如 2022-12-01
        var time = runMonth;
        
        // 删除原有月工资记录
        // 根据月份确定删除的时间区间
        Date startDate, endDate;
        //定义一个参数到createMonthBill里
        var createTime=DateUtil.endOfMonth(time);

        var targetMonth = DateUtil.month(time); // 获取月份(0-11)
        
        if (targetMonth == 0) { // 1月
            // 1月工资：1.1-1.30
            startDate = DateUtil.beginOfMonth(time); // 1.1
            endDate = DateUtil.offsetDay(DateUtil.endOfMonth(time), -1); // 1.30
        } else if (targetMonth == 11) { // 12月
            // 12月工资：11.30-12.31
            startDate = DateUtil.offsetDay(DateUtil.endOfMonth(DateUtil.offsetMonth(time, -1)), -1); // 11.30
            endDate = DateUtil.endOfMonth(time); // 12.31
        } else { // 2-11月
            // 其他月份：上月最后一天到当月倒数第二天
            startDate = DateUtil.endOfMonth(DateUtil.offsetMonth(time, -1)); // 上月最后一天
            endDate = DateUtil.offsetDay(DateUtil.endOfMonth(time), -1); // 当月倒数第二天
        }

        var oldCount = queryHelper.entityQuery(MonthlyWage.class)
                .where(p -> p.time().rangeClosed(startDate, endDate))
                .count();
        if (oldCount > 0) {
           var reCount = queryHelper.entityQuery().deletable(MonthlyWage.class)
                    .where(p -> p.time().rangeClosed(startDate, endDate))
                    .disableLogicDelete()
                    .allowDeleteStatement(true)
                    .executeRows();
            log.info("重新生成月工资，删除原月工资数量："+reCount);
        }

        createMonthlyBill(createTime);
        resetMonthlyBillRecord(time); // 记录仍然使用原始的月初时间
        return Result.ok();
    }

    /** 记录重新生成月工资单
     *
     * @param time
     */
    private void resetMonthlyBillRecord(Date time) {
        var timeFmt=DateUtils.parseDateToStr("YYYYMM", time);
        var record= queryHelper.entityQuery(RunDataRecord.class)
                .where(p->p.runType().eq(RunDataRecord.MonthlyBillReset))
                .where(p->p.name().eq(timeFmt))
                .firstOrNull();
        if(record!=null)
        {
            record.setValue(record.getValue()+"1");
            record.setUpdatedTime(new Date());
            queryHelper.entityQuery().updatable(record).executeRows();
        }else
        {
            record=new RunDataRecord();
            record.setValue("1");
            record.setRunType(RunDataRecord.MonthlyBillReset);
            record.setName(timeFmt);
            record.setCreatedTime(new Date());
            queryHelper.entityQuery().insertable(record).executeRows();
        }
    }

    /**  计算指定月，月工资
     *
     */

    @Transactional
    public void createMonthlyBill(Date runMonth ) {
        //每月2号凌晨生成上个月
        //日工资使用实际基数进行重算，计算全勤
        //计算上月工资
        //注：所有日期计算使用 DateUtil，自动处理闰年（2月29日）和平年（2月28日）的差异

        final Date time;
        final Date startDate;
        final Date endDate;
        final Date ageTime = DateUtil.beginOfMonth(runMonth);  // 用月初，避免触发 getAgeWage 的月末判断
        
        // 获取当前月份（0-11）
        int currentMonth = DateUtil.month(runMonth);
        
        if (currentMonth == 11) {  // 12月31号
            // 算12月工资：11.30-12.31
            startDate = DateUtil.beginOfDay(DateUtil.offsetDay(DateUtil.endOfMonth(DateUtil.offsetMonth(runMonth, -1)), -1));  // 11.30 00:00:00
            endDate = DateUtil.endOfMonth(runMonth);  // 12.31
            time = runMonth;  // 用12月基数，11.30也用12月基数
        } else if (currentMonth == 0) {  // 1月31号
            // 算1月工资：1.1-1.30
            startDate = DateUtil.beginOfDay(DateUtil.beginOfMonth(runMonth));  // 1.1 00:00:00
            endDate = DateUtil.offsetDay(DateUtil.endOfMonth(runMonth), -1);  // 1.30
            time = runMonth;  // 用1月基数
        } else {  // 其他月份
            // 算当月工资：上月最后一天到当月倒数第二天
            startDate = DateUtil.beginOfDay(DateUtil.endOfMonth(DateUtil.offsetMonth(runMonth, -1)));  // 上月最后一天 00:00:00
            endDate = DateUtil.offsetDay(DateUtil.endOfMonth(runMonth), -1);  // 当月倒数第二天
            time = runMonth;  // 用当月基数，上月最后一天也用当月基数
        }

        try {
            var dictCfg = new WageDictCfg();
            var workingAgeWageM = dictCfg.workingAgeWage; //工龄周期工资
            var workingAgeCycle = dictCfg.workingAgeCycle;    //工龄周期月
            var fullWorkWage = dictCfg.fullWorkWage;      //满勤奖,没有请假、迟到
            var paramsList = new ArrayList<>();
            // 基数配置使用time的月份
            paramsList.add(DateUtil.format(time, "yyyy-MM-dd"));       // 基数月份
            // 统计范围：上月最后一天到当月最后一天
            paramsList.add(DateUtil.format(startDate, "yyyy-MM-dd"));  // 开始日期
            paramsList.add(DateUtil.format(endDate, "yyyy-MM-dd"));    // 结束日期
            paramsList.add(DateUtil.format(startDate, "yyyy-MM-dd"));  // 开始日期（用于考勤统计）
            paramsList.add(DateUtil.format(endDate, "yyyy-MM-dd"));    // 结束日期（用于考勤统计）
            //计算基本累计
            System.out.println("我执行到中部了");
            var dtos = queryHelper.entityQuery().sqlQuery(monthlyDtoSql, MonthlyDto.class, paramsList);
            var mlist = new ArrayList<MonthlyWage>();
            resetMonthlyCfgCache();
            for (var item : dtos) {
                var mitem = new MonthlyWage();
                mitem.setTime(ageTime);
                mitem.setUserId(item.getUserId());
                mitem.setUserName(item.getUserName());
                mitem.setLineName(item.getLineName());
                mitem.setLv2Name(item.getLv2Name());
                mitem.setPostName(item.getPostName());
                mitem.setOrderNum(item.getOrderNum());
                mitem.setRatioWage(item.getRationWageSum());
                mitem.setFixedWage(item.getFixedWageSum());
                mitem.setPieceworkWage(item.getPieceworkWageSum());
                mitem.setOvertimePay(item.getOvertimePaySum());
                mitem.setMeal(item.getMealSum());
                mitem.setPerformance(item.getPerformanceSum());
                mitem.setWorkedDays(item.getWorkingDayCount());
                //
                mitem.setInsurance(item.getInsurance());
                mitem.setWorkingDays(getMonthlyCfg(ageTime, item.getLineId()).getWorkingDays());
                if (mitem.getWorkedDays() >= mitem.getWorkingDays()) {
                    // 使用工资计算的时间区间来检查考勤
                    var pcount = attendIncompleteCount(startDate, endDate, mitem.getUserId());
                    if (pcount <= 0) //没有请假、迟到
                        mitem.setWorkedDaysWage(fullWorkWage);
                }

                //工龄、岗位
                var workedRation = mitem.getWorkedDays() / mitem.getWorkingDays();//出勤率
                workedRation = workedRation > 1 ? 1 : workedRation;
                mitem.setPostWage(item.getPostAllowanceCfg() * workedRation);
                if (item.getEntryTime() != null) {
                    var ageWage = (long) (getAgeWage(item.getEntryTime(), ageTime) * workedRation);
                    mitem.setWorkingAge((double) ageWage);
                }

                //mitem.setWageTotal();
                mitem.wageTotalSetBy();
                mitem.salarySetBy();

                //
                mitem.setCreatedTime(new Date());
                mlist.add(mitem);
            }
            queryHelper.entityQuery().insertable(mlist).executeRows();
        } catch (Exception ex) {
            log.error("创建月工资异常",ex);
            throw ex;
        }
    }

    /** 获取月考勤是否有异常或请假
     *
     * @return
     */
    /** 获取指定时间区间内的考勤异常或请假记录数
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @param userId 用户ID
     * @return 异常记录数
     */
    long attendIncompleteCount(Date startDate, Date endDate, Long userId) {
        var startDateStr = DateUtils.parseDateToStr("YYYYMMDD", startDate);
        var endDateStr = DateUtils.parseDateToStr("YYYYMMDD", endDate);
        var count = queryHelper.queryable(AttendanceMaxDto.class)
                .where(a -> a.eq(AttendanceMaxDto::getUserId,userId))
                .where(a -> a
                        .ge(AttendanceMaxDto::getDateFmt, startDateStr)
                        .le(AttendanceMaxDto::getDateFmt, endDateStr)
                        .or()
                        .ge(AttendanceMaxDto::getLeaveDate, startDateStr)
                        .le(AttendanceMaxDto::getLeaveDate, endDateStr)
                )
// 状态匹配（OR关系）
                .where(a -> a
                        .in(AttendanceMaxDto::getResultExt, new Integer[]{1, 2})
                        .or()
                        .in(AttendanceMaxDto::getLeaveType, new String[]{"0", "1"})
                ).count();
        return count;
    }
    /** 月工龄工资计算
     * @param entryTime
     * @param time

     * @return
     */
    private static long getAgeWage(Date entryTime, Date time) {
        var dictCfg=WageDictCfg.cfg();
        var workingAgeWageM =dictCfg.workingAgeWage; //工龄周期工资
        var workingAgeCycle = dictCfg.workingAgeCycle;    //工龄周期月
        var minDate=DateUtil.parse("20160601").toJdkDate(); //工龄开始于2016-7-1

        if(entryTime.compareTo(minDate)<0)
        {
            entryTime=minDate;
        }

        entryTime=DateUtil.beginOfMonth(entryTime);
        
        // 获取计算时间点
        var calcTime = time;
        var isLastDayOfMonth = DateUtil.isLastDayOfMonth(time);
        // DateUtil.month()返回0-11，其中11表示12月
        var isLastDayOfYear = DateUtil.month(time) == 11 && DateUtil.dayOfMonth(time) == 31;
        
        // 如果是月末（但不是年末），计算下个月的工龄
        if (isLastDayOfMonth && !isLastDayOfYear) {
            calcTime = DateUtil.offsetMonth(time, 1);
        }
        
        calcTime = DateUtil.endOfMonth(calcTime);

        var mAge = DateUtil.betweenMonth(entryTime, calcTime, false);
        var cycle=(long)(mAge / workingAgeCycle);
        if(cycle>2&&cycle%2==1) //除首年外，一年一增
        {
            cycle--;
        }
        var ageWage=(long)(cycle* workingAgeWageM );
        if(ageWage>500) //工龄工资500封顶
            ageWage=500;
        return ageWage;
    }

    DefaultKeyValue<Date,List<MonthlyCfg>> monthlyCfgCache=new DefaultKeyValue<>();
    public void  resetMonthlyCfgCache(){
        monthlyCfgCache=new DefaultKeyValue<>();
    }
    private MonthlyCfg getMonthlyCfg(Date time,Long lineId) {
        // 计算基数的时间点
        final Date calcTime;
        
        // 判断是否月末
        if (DateUtil.isLastDayOfMonth(time)) {
            // 判断是否年末（12月）
            if (DateUtil.month(time) == 11 && DateUtil.dayOfMonth(time) == 31) {
                // 年末用当月
                calcTime = time;
            } else {
                // 月末用下月
                calcTime = DateUtil.offsetMonth(time, 1);
            }
        } else {
            calcTime = time;
        }

        List<MonthlyCfg> list = monthlyCfgCache.getValue();
        if (monthlyCfgCache.getKey() == null || calcTime.compareTo(monthlyCfgCache.getKey()) != 0) {
            list = queryHelper.entityQuery(MonthlyCfg.class)
                    .where(p -> p.month().rangeClosed(DateUtil.beginOfMonth(calcTime), DateUtil.endOfMonth(calcTime)))
                    .toList();
            monthlyCfgCache = new DefaultKeyValue<>(calcTime, list);
        }
        if (list.isEmpty())
            return null;
        if (lineId == null)
            return list.get(0);
        var reList = list.stream().filter(p -> Objects.equals(p.getLineId(), lineId)).collect(Collectors.toList());
        var cfg=reList.isEmpty()?null:reList.get(0);
        if(cfg==null) {
            cfg = addMonthlyCfgByBefore(calcTime, lineId);
            if (cfg == null) {
                cfg = new MonthlyCfg();
                cfg.setLineId(lineId);
                cfg.setMonth(calcTime);
            }
            list.add(cfg);
        }
        if (cfg.getId() != null)
            return cfg;
        return null;
    }
    public MonthlyCfg addMonthlyCfgByBefore(Date time,Long lineId){
        //首先找当前产线，找不到找最新记录
//        var mcfg = queryHelper.entityQuery(MonthlyCfg.class)
//                .where(p -> p.month().rangeOpen(DateUtil.beginOfMonth(time), DateUtil.endOfMonth(time)))
//                .where(m -> m.lineId().eq(lineId))
//                .firstOrNull();

           var mcfg = queryHelper.entityQuery(MonthlyCfg.class)
                    .where(p->p.lineId().eq(lineId))
                    .orderBy(p -> p.month().desc())
                    .limit(10)
                    .firstOrNull();
           if(mcfg==null)
               return null;
            mcfg.setMonth(time);
            mcfg.setLineId(lineId);
            mcfg.setStatus(0);
            mcfg.setApprovalRecord(null);
            mcfg.setCreatedTime(new Date());
            mcfg.setUpdatedTime(new Date());
            mcfg.setCreatedId(null);
            mcfg.setUpdatedId(null);
            mcfg.setId(null);
            queryHelper.query().insertable(mcfg).executeRows(true);

        return mcfg;
    }

    public Result<Long> editMonthlyBill(List<MonthlyWage> list){
        var ids=list.stream().map(p->p.getId()).collect(Collectors.toList());
        var olds=queryHelper.entityQuery(MonthlyWage.class).whereByIds(ids).toList();
        if(ids.size()!=olds.size())
            return Result.bad("id无法找到");
        for (var old :olds){
            list.stream().filter(p-> Objects.equals(p.getId(), old.getId())).findAny().ifPresent(p->{
                old.setTaxes(p.getTaxes());
                old.setOther(p.getOther());
                old.setInsurance(p.getInsurance());
                old.salarySetBy();
            });
        }
        var re=queryHelper.entityQuery().updatable(olds).executeRows();
        return Result.ok(re);
    }
    final String monthlyDtoSql="SELECT a.*, b.*, pcfg.post_allowance post_allowance_cfg,pcfg.insurance insurance, emp.entry_time, emp.order_num order_num FROM ( SELECT user_id user_id_a,max(line_id) line_id, sum( score * base1 ) ratio_wage_sum, sum( piecework_wage ) piecework_wage_sum, sum( fixed_wage ) fixed_wage_sum, sum( overtime_pay ) overtime_pay_sum, SUM( meal ) meal_sum, sum( performance ) performance_sum, sum( other_wage ) other_wage_sum, sum(score) score,sum(score_add) score_add FROM ( SELECT dw.*,sdept.line_id, CASE post_rank WHEN 0 THEN mcfg.lv1_final WHEN 1 THEN mcfg.lv2_final WHEN 2 THEN mcfg.lv3_final ELSE 0 END base1 FROM pay_daily_wage dw left join sys_dept sdept on dw.dept_id=sdept.dept_id left join pay_monthly_cfg mcfg on mcfg.line_id=sdept.line_id and date_format( ?, '%Y%m' )= date_format( mcfg.`month`, '%Y%m' ) WHERE dw.working_status = '0' AND dw.STATUS = 1 AND dw.`time` >= ? AND dw.`time` <= ? ) ab GROUP BY user_id ) a LEFT JOIN ( SELECT * FROM ( SELECT max( id ) id1, count( 1 ) days_count, SUM( CASE working_status WHEN 0 THEN 1 ELSE 0 END ) working_day_count FROM pay_daily_wage WHERE working_status = '0' AND post_status = 0 AND `status` = 1 AND `time` >= ? AND `time` <= ? GROUP BY user_id ORDER BY user_id DESC ) m LEFT JOIN pay_daily_wage d ON m.id1 = d.id ) b ON a.user_id_a = b.user_id LEFT JOIN pay_post_cfg pcfg ON pcfg.id = b.post_id LEFT JOIN pay_employee emp ON a.user_id_a = emp.user_id";


}
